/*
 * Copyright 2012 Jin Kwon <jinahya at gmail.com>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package com.googlecode.w3c.widgets.configuration;


import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;


/**
 *
 * @author Jin Kwon <jinahya at gmail.com>
 */
public class WidgetWrapper extends AbstractWrapper<Widget> {


    /**
     * Creates a new instance.
     */
    public WidgetWrapper() {
        this(new Widget());
    }


    /**
     * Creates a new instance.
     * <p/>
     * @param widget the widget to be wrapped
     */
    public WidgetWrapper(final Widget widget) {
        super(widget);
    }


    /**
     * Returns the wrapped widget.
     *
     * @return th wrapped widget
     */
    public Widget getWidget() {
        return getWrappee();
    }


    /**
     *
     * @param <T>
     * @param type
     * <p/>
     * @return
     */
    private <T> Collection<T> getChildren(final Class<T> type) {

        wrappee.getChildren().removeAll(Collections.singleton(null));

        final Collection<T> children = new ArrayList<T>();

        for (final Iterator<?> i = wrappee.getChildren().iterator();
             i.hasNext();) {
            final Object child = i.next();
            if (type.isInstance(child)) {
                children.add(type.cast(child));
            }
        }

        return children;
    }


    /**
     *
     * @param <T>
     * @param type
     * @param elements
     */
    private <T> void setChildren(final Class<T> type,
                                 final Collection<T> elements) {

        for (final Iterator<?> i = wrappee.getChildren().iterator();
             i.hasNext();) {
            final Object element = i.next();
            if (type.isInstance(element)) {
                i.remove();
            }
        }

        wrappee.getChildren().addAll(elements);

        wrappee.getChildren().removeAll(Collections.singleton(null));
    }


    /**
     * Returns names.
     *
     * @return names
     */
    public Collection<Name> getNames() {

        return getChildren(Name.class);
    }


    /**
     * Returns names as a map of
     * <code>@lang</code>s and
     * <code>Name</code>s.
     *
     * @return names
     */
    public Map<String, Name> getNamesMappedByLanguages() {

        final Map<String, Name> names = new LinkedHashMap<String, Name>();

        for (Name name : getNames()) {
            final String lang = name.getLang();
            if (!names.containsKey(lang)) {
                names.put(lang, name);
            }
        }

        return names;
    }


    /**
     * Replaces names.
     *
     * @param names new names.
     */
    public void setNames(final Collection<Name> names) {

        if (names == null) {
            throw new NullPointerException("null names");
        }

        final Map<String, Name> mapped = new LinkedHashMap<String, Name>();

        for (Name name : names) {
            final String lang = name.getLang();
            if (!mapped.containsKey(lang)) {
                mapped.put(lang, name);
            }
        }

        setNames(mapped);
    }


    /**
     * Replaces names with given map of
     * <code>@lang</code>s and
     * <code>Name</code>s.
     *
     * @param names new names
     */
    public void setNames(final Map<String, Name> names) {

        if (names == null) {
            throw new NullPointerException("null names");
        }

        for (Entry<String, Name> entry : names.entrySet()) {
            final String lang = entry.getKey();
            final Name name = entry.getValue();
            if (name == null) {
                throw new IllegalArgumentException(
                    "null name for lang(" + lang + ")");
            }
            if ((lang == null && name.getLang() != null)
                || !lang.equals(name.getLang())) {
                throw new IllegalArgumentException(
                    "illegal mapping: lang(" + lang + ") - name.lang("
                    + name.getLang() + ")");
            }
        }

        setChildren(Name.class, names.values());
    }


    /**
     * Returns descriptions.
     *
     * @return descriptions
     */
    public Collection<Description> getDescriptions() {

        return getChildren(Description.class);
    }


    /**
     * Returns descriptions as a map of
     * <code>@lang</code> and
     * <code>Description</code>s.
     *
     * @return descriptions
     */
    public Map<String, Description> getDescriptionsMappedByLanguages() {

        final Map<String, Description> descriptions =
            new LinkedHashMap<String, Description>();

        for (Description description : getDescriptions()) {
            final String lang = description.getLang();
            if (!descriptions.containsKey(lang)) {
                descriptions.put(lang, description);
            }
        }

        return descriptions;
    }


    /**
     * Replaces descriptions.
     *
     * @param descriptions descriptions
     */
    public void setDescriptions(final Collection<Description> descriptions) {

        if (descriptions == null) {
            throw new NullPointerException("null descriptions");
        }

        final Map<String, Description> mapped =
            new LinkedHashMap<String, Description>();

        for (Description description : descriptions) {
            final String lang = description.getLang();
            if (!mapped.containsKey(lang)) {
                mapped.put(lang, description);
            }
        }

        setDescriptions(mapped);
    }


    /**
     * Replaces descriptions with given map of
     * <code>@lang</code>s and
     * <code>Description</code>s.
     *
     * @param descriptions descriptions
     */
    public void setDescriptions(final Map<String, Description> descriptions) {

        if (descriptions == null) {
            throw new NullPointerException("null descriptions");
        }

        for (Entry<String, Description> entry : descriptions.entrySet()) {
            final String lang = entry.getKey();
            final Description description = entry.getValue();
            if (description == null) {
                throw new IllegalArgumentException(
                    "null description for lang(" + lang + ")");
            }
            if ((lang == null && description.getLang() != null)
                || !lang.equals(description.getLang())) {
                throw new IllegalArgumentException(
                    "illegal mapping: lang(" + lang + ") - description.lang("
                    + description.getLang() + ")");
            }
        }

        setChildren(Description.class, descriptions.values());
    }


    /**
     * Returns author.
     *
     * @return author or null
     */
    public Author getAuthor() {

        final Iterator<Author> i = getChildren(Author.class).iterator();

        if (i.hasNext()) {
            return i.next();
        }

        return null;
    }


    /**
     * Replaces author.
     *
     * @param author new author
     */
    public void setAuthor(final Author author) {

        setChildren(Author.class, Collections.singleton(author));
    }


    /**
     * Returns licenses.
     *
     * @return licenses
     */
    public Collection<License> getLicenses() {

        return getChildren(License.class);
    }


    /**
     * Returns licenses as a map of
     * <code>@lang</code>s and
     * <code>License</code>s.
     *
     * @return licenses.
     */
    public Map<String, License> getLicensesMappedByLanguages() {

        final Map<String, License> licenses =
            new LinkedHashMap<String, License>();

        for (License license : getLicenses()) {
            final String lang = license.getLang();
            if (!licenses.containsKey(lang)) {
                licenses.put(lang, license);
            }
        }

        return licenses;
    }


    /**
     * Replaces licenses.
     *
     * @param licenses new licenses
     */
    public void setLicenses(final Collection<License> licenses) {

        if (licenses == null) {
            throw new NullPointerException("null licenses");
        }

        final Map<String, License> mapped =
            new LinkedHashMap<String, License>();

        for (License license : licenses) {
            final String lang = license.getLang();
            if (!mapped.containsKey(lang)) {
                mapped.put(lang, license);
            }
        }

        setLicenses(mapped);
    }


    /**
     * Replaces licenses with given map of
     * <code>@lang</code>s and
     * <code>License</code>s.
     *
     * @param licenses new licenses
     */
    public void setLicenses(final Map<String, License> licenses) {

        if (licenses == null) {
            throw new NullPointerException("null licenses");
        }

        for (Entry<String, License> entry : licenses.entrySet()) {
            final String lang = entry.getKey();
            final License license = entry.getValue();
            if (license == null) {
                throw new IllegalArgumentException(
                    "null license for lang(" + lang + ")");
            }
            if ((lang == null && license.getLang() != null)
                || !lang.equals(license.getLang())) {
                throw new IllegalArgumentException(
                    "illegal mapping: lang(" + lang + ") - license.lang("
                    + license.getLang() + ")");
            }
        }

        setChildren(License.class, licenses.values());
    }


    /**
     * Returns icons.
     *
     * @return icons
     */
    public Collection<Icon> getIcons() {

        return getChildren(Icon.class);
    }


    /**
     * Returns icons as a map of
     * <code>@src</code>s and
     * <code>Icon</code>s.
     *
     * @return icons
     */
    public Map<String, Icon> getIconsMappedBySources() {

        final Map<String, Icon> icons = new LinkedHashMap<String, Icon>();

        for (Icon icon : getIcons()) {
            final String src = icon.getSrc();
            if (!icons.containsKey(src)) {
                icons.put(src, icon);
            }
        }

        return icons;
    }


    /**
     * Replaces icons.
     *
     * @param icons new icons
     */
    public void setIcons(final Collection<Icon> icons) {

        if (icons == null) {
            throw new NullPointerException("null icons");
        }

        final Map<String, Icon> mapped = new LinkedHashMap<String, Icon>();

        for (Icon icon : icons) {
            final String src = icon.getSrc();
            if (src == null) {
                throw new IllegalArgumentException("null src");
            }
            if (mapped.put(src, icon) != null) {
                throw new IllegalArgumentException("duplicated src: " + src);
            }
        }

        setIcons(mapped);
    }


    /**
     * Replaces icons with specified map of
     * <code>@src</code>s and
     * <code>Icon</code>s.
     *
     * @param icons a map of
     * <code>@src</code>s and
     * <code>Icon</code>s.
     */
    public void setIcons(final Map<String, Icon> icons) {

        if (icons == null) {
            throw new NullPointerException("null icons");
        }

        for (Entry<String, Icon> entry : icons.entrySet()) {
            final String src = entry.getKey();
            if (src == null || src.trim().isEmpty()) {
                throw new IllegalArgumentException("null or empty src");
            }
            final Icon icon = entry.getValue();
            if (icon == null) {
                throw new IllegalArgumentException(
                    "null icon for src(" + src + ")");
            }
            if (!src.equals(icon.getSrc())) {
                throw new IllegalArgumentException(
                    "illegal mapping: src(" + src + ") - icon.src("
                    + icon.getSrc() + ")");
            }
        }

        setChildren(Icon.class, icons.values());
    }


    /**
     * Returns contents.
     *
     * @return contents
     */
    public Collection<Content> getContents() {

        return getChildren(Content.class);
    }


    /**
     * Returns contents as a map of
     * <code>@src</code>s and
     * <code>Content</code>s.
     *
     * @return contents
     */
    public Map<String, Content> getContentsMappedBySources() {

        final Map<String, Content> contents =
            new LinkedHashMap<String, Content>();

        for (Content content : getContents()) {
            final String src = content.getSrc();
            if (!contents.containsKey(src)) {
                contents.put(src, content);
            }
        }

        return contents;
    }


    /**
     * Replaces contents.
     *
     * @param contents contents
     */
    public void setContents(final Collection<Content> contents) {

        if (contents == null) {
            throw new NullPointerException("null contents");
        }

        final Map<String, Content> mapped =
            new LinkedHashMap<String, Content>();

        for (Content content : contents) {
            if (content == null) {
                throw new IllegalArgumentException("null content");
            }
            final String src = content.getSrc();
            if (src == null || src.trim().isEmpty()) {
                throw new IllegalArgumentException("null or empty @src");
            }
            if (mapped.put(src, content) != null) {
                throw new IllegalArgumentException("duplicated @src: " + src);
            }
        }

        setContents(mapped);
    }


    /**
     * Replaces contents with given map of
     * <code>@src</code>s and
     * <code>Content</code>s.
     *
     * @param contents new contents
     */
    public void setContents(final Map<String, Content> contents) {

        if (contents == null) {
            throw new NullPointerException("null contents");
        }

        for (Entry<String, Content> entry : contents.entrySet()) {
            final String src = entry.getKey();
            if (src == null || src.trim().isEmpty()) {
                throw new IllegalArgumentException("null or empty src");
            }
            final Content content = entry.getValue();
            if (content == null) {
                throw new IllegalArgumentException(
                    "null content for src(" + src + ")");
            }
            if (!src.equals(content.getLang())) {
                throw new IllegalArgumentException(
                    "illegal mapping: src(" + src + ") - content.src("
                    + content.getSrc() + ")");
            }
        }

        setChildren(Content.class, contents.values());
    }


    /**
     * Returns features.
     *
     * @return features.
     */
    public Collection<Feature> getFeatures() {

        return getChildren(Feature.class);
    }


    /**
     * Returns features as a map of
     * <code>@name</code>s and
     * <code>Feature</code>s.
     *
     * @return features
     */
    public Map<String, Feature> getFeaturesMappedByNames() {

        final Map<String, Feature> features =
            new LinkedHashMap<String, Feature>();

        for (Feature feature : getFeatures()) {
            final String name = feature.getName();
            if (!features.containsKey(name)) {
                features.put(name, feature);
            }
        }

        return features;
    }


    /**
     * Replaces features.
     *
     * @param features new features
     */
    public void setFeatures(final Collection<Feature> features) {

        if (features == null) {
            throw new NullPointerException("null features");
        }

        final Map<String, Feature> mapped =
            new LinkedHashMap<String, Feature>();

        for (Feature feature : features) {
            if (feature == null) {
                throw new IllegalArgumentException("null feature");
            }
            final String name = feature.getName();
            if (name == null || name.trim().isEmpty()) {
                throw new IllegalArgumentException("null or empty @name");
            }
            if (mapped.put(name, feature) != null) {
                throw new IllegalArgumentException("duplicated @name: " + name);
            }
        }

        setFeatures(mapped);
    }


    /**
     * Replaces features with specified map of
     * <code>@name</code>s and
     * <code>Feature</code>s.
     *
     * @param features new features
     */
    public void setFeatures(final Map<String, Feature> features) {

        if (features == null) {
            throw new NullPointerException("null features");
        }

        for (Entry<String, Feature> entry : features.entrySet()) {
            final String name = entry.getKey();
            if (name == null || name.trim().isEmpty()) {
                throw new IllegalArgumentException("null or empty name");
            }
            final Feature feature = entry.getValue();
            if (feature == null) {
                throw new IllegalArgumentException(
                    "null feature for name(" + name + ")");
            }
            if (!name.equals(feature.getName())) {
                throw new IllegalArgumentException(
                    "illegal mapping: name(" + name + ") - feature.name("
                    + feature.getName() + ")");
            }
        }

        setChildren(Feature.class, features.values());
    }


    /**
     * Returns preferences.
     *
     * @return preferences
     */
    public Collection<Preference> getPreferences() {

        return getChildren(Preference.class);
    }


    /**
     * Returns preferences as a map of
     * <code>@name</code>s and
     * <code>Preference</code>s.
     *
     * @return preferences
     */
    public Map<String, Preference> getPreferencesMappedByNames() {

        final Map<String, Preference> preferences =
            new LinkedHashMap<String, Preference>();

        for (Preference preference : getPreferences()) {
            final String name = preference.getName();
            if (!preferences.containsKey(name)) {
                preferences.put(name, preference);
            }
        }

        return preferences;
    }


    /**
     * Replaces preferences.
     *
     * @param preferences new preferences
     */
    public void setPreferences(final Collection<Preference> preferences) {

        if (preferences == null) {
            throw new NullPointerException("null preferences");
        }

        final Map<String, Preference> mapped =
            new LinkedHashMap<String, Preference>();

        for (Preference preference : preferences) {
            if (preference == null) {
                throw new IllegalArgumentException("null preference");
            }
            final String name = preference.getName();
            if (name == null || name.trim().isEmpty()) {
                throw new IllegalArgumentException("null or empty name");
            }
            if (mapped.put(name, preference) != null) {
                throw new IllegalArgumentException("duplicated name: " + name);
            }
        }

        setPreferences(mapped);
    }


    /**
     * Replaces preferences with given map of
     * <code>@name</code>s and
     * <code>Preference</code>s.
     *
     * @param preferences new preferences
     */
    public void setPreferences(final Map<String, Preference> preferences) {

        if (preferences == null) {
            throw new NullPointerException("null preferences");
        }

        for (Entry<String, Preference> entry : preferences.entrySet()) {
            final String name = entry.getKey();
            if (name == null || name.trim().isEmpty()) {
                throw new IllegalArgumentException("null or empty name");
            }
            final Preference preference = entry.getValue();
            if (preference == null) {
                throw new IllegalArgumentException(
                    "null preference for name(" + name + ")");
            }
            if (!name.equals(preference.getName())) {
                throw new IllegalArgumentException(
                    "illegal mapping: name(" + name + ") - preference.name("
                    + preference.getName() + ")");
            }
        }

        setChildren(Preference.class, preferences.values());
    }


}

